/**
 * Copyright 2005-2007 Metaweb Technologies, Inc.  All Rights Reserved.
 *
 * <b>Iso8601Date</b>
 *
 * @author nick@metaweb.com
 */
 
function Iso8601Date(str) {
  this._dateObject = new Date(0,0);
  this._dateFormat = "";  
  this._validIsoString = "";

  if (str.match(/am|pm|[0-9]\s*h|o\'clock/i)) {
    str = this.forceTimeFormat (str);
  }

  var myRegExp = new RegExp("^(.*?)([0-9]+\:[0-9]+(?:\:[0-9]+(?:\.[0-9]*)?)?)?([^\:]*)$");
  var comps = myRegExp.exec(str);

  if (comps[1].indexOf(":") >= 0) { comps[1] = ""; }
  if (comps[3].indexOf(":") >= 0) { comps[3] = ""; }

  var dateString = comps[1] + " " + comps[3];

  if (dateString.length > 1) {
   this._validIsoString = this.setDate(dateString);
  }

  var timeString = comps[2];
  
  if (timeString) {
    if (dateString.length > 1) {
      if (this._dateFormat == 'Invalid') {
        this._dateFormat = '';
        this._validIsoString = '';
      } else {
        this._dateFormat = this._dateFormat + "T";
        this._validIsoString = this._validIsoString + "T";
      }
    }      
    this._validIsoString = this._validIsoString + this.setTime(timeString);
  }

};


Iso8601Date.monthMap = {
      'january': '01',
      'jan': '01',
      'february': '02',
      'feb': '02',
      'march': '03',
      'mar': '03', 
      'april': '04',
      'apr': '04',
      'may':'05',
      'june':'06',
      'jun':'06',
      'july': '07',
      'jul': '07',
      'august': '08',
      'aug': '08',
      'september': '09',
      'sep': '09',
      'october': '10',
      'oct': '10',
      'november': '11',
      'nov': '11',
      'december': '12',
      'dec': '12'
};


Iso8601Date.VALID_FORMATS = [
  "YYYY-MM-DDThh:mm:ss.s[Z]",  
  "YYYY-MM-DDThh:mm:ss[Z]",
  "YYYY-MM-DDThh:mm[Z]",
  "YYYY-MM-DDThh[Z]",
  "YYYY-MM-DD",
  "YYYY-MM",
  "YYYY",
  "hh:mm:ss.s[Z]",
  "hh:mm:ss[Z]",
  "hh:mm[Z]"
];

Iso8601Date.prototype.getDateObject = function() {
  return (this._dateObject);
};

Iso8601Date.prototype.getDateFormat = function() {
  return (this._dateFormat);
};

Iso8601Date.prototype.getIsoString = function() {
  return (this._validIsoString);
};

Iso8601Date.prototype.setDate = function(str) {
  var fullRegexp = "^(-?[0-9]{4})(?:-?([0-9]{2})(?:-?([0-9]{2}))?)?$";
  var dd = str.match(new RegExp(fullRegexp));

  if (!(dd)) {
   str = this.forceDateFormat(str);
  }

  //now parse the date
  var regexp = "^(-?[0-9]{4})$";
  var d = str.match(new RegExp(regexp));
  if (d) {
    this._dateFormat = 'YYYY'
  } else {
    regexp =  "^(-?[0-9]{4})-?([0-9]{2})$";
    d = str.match(new RegExp(regexp));
    if (d) {
      this._dateFormat = 'YYYY-MM'
    } else {
      regexp =  "^(-?[0-9]{4})-?([0-9]{2})-?([0-9]{2})$";
      d = str.match(new RegExp(regexp));
      if (d) {
        this._dateFormat = 'YYYY-MM-DD'
      } else {
        this._dateFormat= 'Invalid';
	//throw "Invalid Iso8601 date string: "  + str + "\nValid Iso8601 dates are in the form: \n" + Iso8601Date.VALID_FORMATS.join("\n");
        return; 
      }
    }
  }

 
  var year = d[1];
  var month = d[2];
  var date = d[3];

  if ((month > 12) || (date > 31)) {
    this._dateFormat= 'Invalid';
    return;
  }

  this._dateObject.setUTCFullYear(year);
  if (month) {
    this._dateObject.setUTCDate(1);
    this._dateObject.setUTCMonth(month - 1);
    if (date) {
      this._dateObject.setUTCDate(date);
    }
  }

  //Need to set str based on dateObject, for cases like 2006-10-35
  var thisDate = this._dateObject.getDate() + ""; 
  var thisMonth = this._dateObject.getMonth() + 1; 
  thisMonth = thisMonth + ""; // convert to string
  if (thisMonth.length ==1) {thisMonth = "0" + thisMonth;}
  if (thisDate.length ==1) {thisDate = "0" + thisDate;}

  var fullYear = this._dateObject.getFullYear(); // getFullYear doesn't show leading zeros.
  if ((fullYear > 1000) || (fullYear < -1000)) {
    fullYear = String(fullYear);
  } else {
    fullYear = String(fullYear);
    if (fullYear >= 0) { 
      while (fullYear.length < 4) { fullYear = "0" + fullYear + "";}
    } 
    else {
      fullYear = 0 - fullYear;
      fullYear = String(fullYear);
      while (fullYear.length < 4) { fullYear = "0" + fullYear;}
      fullYear = String("-" + fullYear);
    }
  }

  if (this._dateFormat == 'YYYY') {
    str = fullYear;
  } else if (this._dateFormat == 'YYYY-MM') {
    str = fullYear + "-" + thisMonth;
  } else if (this._dateFormat == 'YYYY-MM-DD') {
    str = fullYear + "-" + thisMonth + "-" + thisDate;
  }
  return (str);
};


Iso8601Date.prototype.forceTimeFormat = function(str) {

  if (str.match(/o\'clock|am|pm/)) {
    var hoursMins = new Array();  

    strA = " " + str;
//   hoursMins = strA.match(/[^0-9\.]([0-2]?[0-9])\s*\:?([0-5][0-9])?\s*(o'clock|am|pm)/);
     hoursMins = strA.match(/[^0-9\.\:]([0-2]?[0-9])\s*(\:[0-5][0-9])?\s*(o\'clock|am|pm)/);


    if (!(hoursMins)) {return (str);} 
    // Cannot say 08:08:08.08 pm, that's asinine. If you specify secs or millisecs, must use 24 hour clock. (for now)

    var hours = hoursMins[1];
    if (hoursMins[2]) {
      var mins = hoursMins[2];
      if (mins.indexOf(":") == 0) {
        mins = mins.substring(1);
      }
    } else {
      var mins = "00" + "";
    }

    if ((hoursMins[3] == "am") && (hours == 12)) {
     hours = "00" + "";
    }
    if ((hoursMins[3] == "pm") && (hours < 12)) {
         // The following doesn't work:   hours = hours.valueOf() + 12;
      hours = ++hours + 11;  // A hack! (but works perfectly)  
    } else { 
      if (hours.length ==1) {hours = "0" + hours + "";}
    }
    str=str.replace(/([0-2]?[0-9])\s*\:?([0-5][0-9])?\s*(o\'clock|am|pm)/, hours + ":" + mins);
    return(str);
  } else {  //Not am|pm|o\'clock, so must be [0-9]h
    var hoursMins = new Array();  
    hoursMins = str.match(/([0-2]?[0-9])\s*h\s*([0-5][0-9])?\s*(m)?/);
    var hours = hoursMins[1];
    if (hours.length ==1) {hours = "0" + hours + "";}
    var mins = "00" + ""; 
    if (hoursMins[3] == 'm') {
      mins = hoursMins[2];
    }

    str = str.replace(/([0-2]?[0-9])\s*h\s*([0-5][0-9])?\s*(m)?/, hours + ":" + mins);
    return(str);

  }
}


Iso8601Date.prototype.forceDateFormat = function(str) {
  var hasAlpha = str.match(new RegExp("[a-zA-Z]"));
  if (hasAlpha) {
      if (str.match(new RegExp(/today/i))) {
        var nowObj = new Date();
        var thisMonth = nowObj.getMonth() + 1; // Numeric addition
        thisMonth = thisMonth + ""; // Now convert to a string.
        var thisDate =  nowObj.getDate() + "";
        if (thisMonth.length ==1) {thisMonth = "0" + thisMonth;}
        if (thisDate.length ==1) {thisDate = "0" + thisDate;}
        str = nowObj.getFullYear() + "-" + thisMonth + "-" + thisDate;
	return (str);
      }
      if (str.match(new RegExp(/yesterday/i))) {
        var nowObj = new Date();
        var nowTime = nowObj.getTime();
        var yesterday = nowTime - (24*3600000);
        yesObj = new Date(yesterday);
        var thisMonth = yesObj.getMonth() + 1; // Numeric addition
        thisMonth = thisMonth + ""; // Now convert to a string.
        var thisDate =  yesObj.getDate() + "";
        if (thisMonth.length ==1) {thisMonth = "0" + thisMonth;}
        if (thisDate.length ==1) {thisDate = "0" + thisDate;}
        str = yesObj.getFullYear() + "-" + thisMonth + "-" + thisDate;
	return (str);
      } 
      if (str.match(new RegExp(/tomorrow/i))) {
        var nowObj = new Date();
        var nowTime = nowObj.getTime();
        var tomorrow = nowTime + (24*3600000);
        yesObj = new Date(tomorrow);
        var thisMonth = yesObj.getMonth() + 1; // Numeric addition
        thisMonth = thisMonth + ""; // Now convert to a string.
        var thisDate =  yesObj.getDate() + "";
        if (thisMonth.length ==1) {thisMonth = "0" + thisMonth;}
        if (thisDate.length ==1) {thisDate = "0" + thisDate;}
        str = yesObj.getFullYear() + "-" + thisMonth + "-" + thisDate;
	return (str);
      } 

      var DOWReg = new RegExp(/sunday|monday|tuesday|wednesday|thursday|friday|saturday/i);
      var regexRes = DOWReg.exec(str)
      if (regexRes) {
          //assume they mean the following one, ie "tuesday" == "this tuesday" == "next tuesday"
        var givenDOWstring = regexRes[0].toLowerCase();
        var DOWstrings = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
        for (i=0;i<=6;i++) {
          if (DOWstrings[i] == givenDOWstring) {
            givenDOW = i;
          }
        }
        var nowObj = new Date();
        var nowTime = nowObj.getTime();
	var dayDiff = givenDOW - nowObj.getDay();
        if (dayDiff < 1) {dayDiff = dayDiff + 7;}
        var dowTime = nowTime + (dayDiff * 24 * 3600000);

        dowObj = new Date(dowTime);
        var dowMonth = dowObj.getMonth() + 1; // Numeric addition
        dowMonth = dowMonth + ""; // Now convert to a string.
        var dowDate =  dowObj.getDate() + "";
        if (dowMonth.length ==1) {dowMonth = "0" + dowMonth;}
        if (dowDate.length ==1) {dowDate = "0" + dowDate;}
        str = dowObj.getFullYear() + "-" + dowMonth + "-" + dowDate;
	return (str);        
      }

     var givenMonth = false;

     str = str.replace(/(January|February|March|April|May|June|July|August|September|October|November|December|Jan|Feb|Mar|Apr|Jun|Jul|Aug|Sep|Oct|Nov|Dec)(\s|\/|\,|-|$)/i, function (p1) {
	
        p1 = String(p1);
	p1 = p1.replace(/\s|\/|\,|-/, '');
        var monthNum = Iso8601Date.monthMap[p1.toLowerCase()];
        if (monthNum) {
	  givenMonth = monthNum;
	}
        return ' ';
      }
     );
  }


  str = "," + str + ",";

  str = str.replace(/([^0-9])([0-9])([^0-9])/g, "$1" + 0+ "$2" + "$3" +"");
  str = str.replace(/([^0-9])([0-9])([^0-9])/g, "$1" + 0+ "$2" + "$3" +"");

  //Now extract dddd as year.
  var yearReg = new RegExp("[^0-9](-)?([0-9]{4})[^0-9]");
  var yearA = yearReg.exec(str);

  if (!(yearA)) {return ("Invalid Date Format");}
  var yearVal = yearA[1] ? ("-" + yearA[2] + "") : yearA[2];

  if (str.match(/bce/i)) {
    // yearVal = 1 - yearVal; // I wish JS had something like sprintf
    yearVal--;
    yearVal = yearVal + "";
    while (yearVal.length < 4) { yearVal = "0" + yearVal + "";}
    yearVal = "-" + yearVal;
  }


/*
  //If no year is given, assume the current year.
  var givenYear=1;
  if (yearA) {
    var yearVal = yearA[1] ? ("-" + yearA[2] + "") : yearA[2];
  } else {
    givenYear=0;
    var nowObj = new Date();
    var yearVal = nowObj.getFullYear();
  }
*/

  if (givenMonth) {
     var dayReg = new RegExp("[^0-9]([0-9]{2})[^0-9]+");
     var dayArray  = dayReg.exec(str);
     if (dayArray) {
	return (yearVal + "-" + givenMonth + "-" + dayArray[1].substring(0,2));
     } else {
	return (yearVal + "-" + givenMonth);
     }
  } else {
    var monDayReg = new RegExp("[^0-9]([0-9]{2})[^0-9]+([0-9]{2}[^0-9])?");
    var monDayArray = monDayReg.exec(str);

    if (!(monDayArray)) {
      //if (givenYear) {
        return (yearVal);
      //} else {
      //  return ("");
      //}
    } else if (monDayArray[2]) {
      return (yearVal + "-" + monDayArray[1] + "-" + monDayArray[2].substring(0,2));
    } else {
      //if (givenYear) {
        return (yearVal + "-" + monDayArray[1]);
      //}
    }
  }
}


Iso8601Date.prototype.setTime = function(str) {
  // first strip timezone info from the end
  var timezone = "Z|(([-+])([0-9]{2})(:?([0-9]{2}))?)$";
  var d = str.match(new RegExp(timezone));
  var offset = 0; // local time if no tz info
  if (d) {
    if (d[0] != 'Z') {
      offset = (Number(d[3]) * 60) + Number(d[5]);
      offset *= ((d[2] == '-') ? 1 : -1);
    }
    str = str.substr(0, str.length - d[0].length);
  }
  offset += this._dateObject.getTimezoneOffset();
 
  // then work out the time
  if (str.indexOf(":") == 1) { str = "0" + str; }
  var regexp = "^([0-9]{2})(?::([0-9]{2})(?::([0-9]{2})(?:\.([0-9]+))?)?)?";
  var d = str.match(new RegExp(regexp));
  if(!d) {
    return('');
    //throw "Invalid Iso8601 time string: "  + str + "\nValid Iso8601 dates are in the form: \n" + Iso8601Date.VALID_FORMATS.join("\n");
  }
  var hours = d[1];
  if (hours > 23) {
    return('');
  }
  var timeFormat = "hh";
  var mins=0;
  if (d[2]) {
    mins = Number(d[2]);
    if (mins > 59) {
      return ('');
    }
    timeFormat = timeFormat + ":mm";
  }
  var secs=0;
  if (d[3]) {
    secs = Number(d[3]);
    if (secs > 59) {
      return ('');
    }
    timeFormat = timeFormat + ":ss";
   
      if (d[4]) { //fractions of a second
      secs = secs + "." + d[4] + "";
      secs = Number(secs);
      timeFormat = timeFormat + ".s";
    }
  } 
  
  this._dateFormat = this._dateFormat + timeFormat;
  this._dateObject.setUTCHours(hours);
  this._dateObject.setUTCMinutes(mins);
  this._dateObject.setUTCSeconds(secs);
    
  var msec = parseFloat(secs - parseInt(secs)) * 1000;
  msec = (Math.round(msec*10000000)) / 10000000; 
  this._dateObject.setUTCMilliseconds(msec);
  if (offset != 0) {
    this._dateObject.setTime(this._dateObject.getTime() + offset * 60000);
  }
  hours = hours + "";
  if (hours.length == 1) {hours = "0" + hours;}
  mins = mins + "";
  if (mins.length == 1) {mins = "0" + mins;}
  secs = secs + "";
  if (secs.length == 1) {secs = "0" + secs;}

  if (timeFormat == "hh") {
    return (hours);
  } else if (timeFormat == "hh:mm") {
    return (hours + ":" + mins);
  } else if (timeFormat == "hh:mm:ss") {
    return (hours + ":" + mins  + ":" + secs);
  } else if (timeFormat == "hh:mm:ss.s") {
   // return (hours + ":" + mins  + ":" + secs + "." + ms);
    return (hours + ":" + mins  + ":" + secs);
  }
};

Iso8601Date.test = function() {
  var good = [
    "2006-07-18T12:34:56",
    "-2006-07-18T12:34",
    "0006-07-18T12:34:56.789",  
    "-0006-07-18T12",
    "2222-07-18",
    "-2222-07-18",
    "0001",
    "-0001",
    "02:34:56.789+13:45",
    "12:34:56Z",
    "22:34"
  ];
  for(var i=0, len=good.length; i<len; i++) {
    try {
      print("------");
      print(good[i]);
      var isoDate = new Iso8601Date(good[i]);
      print(isoDate.getDateFormat());
      print(isoDate.getDateObject());
      print(isoDate.getDateObject().toString());
    }
    catch (ex) {
      Debug.error(ex);
    }
  }  
};
